/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is Forte for Java, Community Edition. The Initial * Developer of the Original Code is Sun Microsystems, Inc. Portions * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved. */ package org.openide.filesystems; import java.io.*; import java.util.*; import java.util.jar.*; /** Common utilities for handling files. * This is a dummy class; all methods are static. * * @author Petr Hamernik * @version 0.12, May 12, 1998 */ public final class FileUtil extends Object { private FileUtil() {} /** Copies stream of files. * @param is input stream * @param os output stream */ public static void copy (InputStream is, OutputStream os) throws IOException { final byte[] BUFFER = new byte[4096]; int len; for (;;) { len = is.read (BUFFER); if (len == -1) return; os.write (BUFFER, 0, len); } } /** Copies file to the selected folder. * This implementation simply copies the file by stream content. * @param source source file object * @param destFolder destination folder * @param newName file name (without extension) of destination file * @param newExt extension of destination file * @return the created file object in the destination folder * @exception IOException if <code>destFolder</code> is not a folder or does not exist; the destination file already exists; or * another critical error occurs during copying */ static FileObject copyFileImpl ( FileObject source, FileObject destFolder, String newName, String newExt ) throws IOException { FileObject dest = destFolder.createData(newName, newExt); FileLock lock = null; InputStream bufIn = null; OutputStream bufOut = null; try { lock = dest.lock(); bufIn = source.getInputStream(); bufOut = dest.getOutputStream(lock); copy (bufIn, bufOut); copyAttributes (source, dest); } finally { if (bufIn != null) bufIn.close(); if (bufOut != null) bufOut.close(); if (lock != null) lock.releaseLock(); } return dest; } // // public methods // /** Copies file to the selected folder. * This implementation simply copies the file by stream content. * @param source source file object * @param destFolder destination folder * @param newName file name (without extension) of destination file * @param newExt extension of destination file * @return the created file object in the destination folder * @exception IOException if <code>destFolder</code> is not a folder or does not exist; the destination file already exists; or * another critical error occurs during copying */ public static FileObject copyFile(FileObject source, FileObject destFolder, String newName, String newExt) throws IOException { return source.copy (destFolder, newName, newExt); } /** Copies file to the selected folder. * This implementation simply copies the file by stream content. * Uses the extension of the source file. * @param source source file object * @param destFolder destination folder * @param newName file name (without extension) of destination file * @return the created file object in the destination folder * @exception IOException if <code>destFolder</code> is not a folder or does not exist; the destination file already exists; or * another critical error occurs during copying */ public static FileObject copyFile(FileObject source, FileObject destFolder, String newName) throws IOException { return copyFile(source, destFolder, newName, source.getExt()); } /** Moves file to the selected folder. * This implementation uses a copy-and-delete mechanism, and automatically uses the necessary lock. * @param source source file object * @param destFolder destination folder * @param newName file name (without extension) of destination file * @return new file object * @exception IOException if either the {@link #copyFile copy} or {@link FileObject#delete delete} failed */ public static FileObject moveFile(FileObject source, FileObject destFolder, String newName) throws IOException { FileLock lock = null; try { lock = source.lock(); return source.move (lock, destFolder, newName, source.getExt ()); } finally { if (lock != null) lock.releaseLock(); } } /** Creates a folder on given file system. The name of * folder can be composed as resource name (e. g. org/netbeans/myfolder) * and the method scans which of folders has already been created * and which not. If successful the caller can be sure that the folder * is there and receives a reference to it. * * @param folder to begin with creation at * @param name name of folder as a resource * @return the folder for given name * @exception IOException if the creation fails */ public static FileObject createFolder (FileObject folder, String name) throws IOException { StringTokenizer st = new StringTokenizer (name, "/"); // NOI18N while (st.hasMoreElements ()) { name = st.nextToken (); if (name.length () > 0) { FileObject f = folder.getFileObject (name); if (f == null) { f = folder.createFolder (name); } folder = f; } } return folder; } /** Creates a data file on given file system. The name of * data file can be composed as resource name (e. g. org/netbeans/myfolder/mydata ) * and the method scans which of folders has already been created * and which not. * * @param folder to begin with creation at * @param name name of data file as a resource * @return the data file for given name * @exception IOException if the creation fails */ public static FileObject createData (FileObject folder, String name) throws IOException { String foldername, dataname, fname, ext; int index = name.lastIndexOf('/'); FileObject data; // names with '/' on the end are not valid if (index >= name.length()) throw new IOException("Wrong file name."); // NOI18N // if name contains '/', create necessary folder first if (index != -1) { foldername = name.substring(0, index); dataname = name.substring(index + 1); folder = createFolder(folder, foldername); } else { dataname = name; } // create data index = dataname.lastIndexOf('.'); if (index != -1) { fname = dataname.substring(0, index); ext = dataname.substring(index + 1); } else { fname = dataname; ext = ""; // NOI18N } data = folder.getFileObject (fname, ext); if (data == null) { data = folder.createData(fname, ext); } return data; } /** transient attributes which should not be copied * of type Set<String> * @associates String */ static final Set transientAttributes = new HashSet (); static { transientAttributes.add ("templateWizardURL"); // NOI18N transientAttributes.add ("templateWizardIterator"); // NOI18N transientAttributes.add ("templateWizardDescResource"); // NOI18N transientAttributes.add ("SystemFileSystem.localizingBundle"); // NOI18N } /** Copies attributes from one file to another. * Note: several special attributes will not be copied, as they should * semantically be transient. These include attributes used by the * template wizard (but not the template atttribute itself). * @param source source file object * @param dest destination file object * @exception IOException if the copying failed */ public static void copyAttributes (FileObject source, FileObject dest) throws IOException { Enumeration attrKeys = source.getAttributes(); while (attrKeys.hasMoreElements()) { String key = (String) attrKeys.nextElement(); if (transientAttributes.contains (key)) continue; Object value = source.getAttribute(key); if (value != null) { dest.setAttribute(key, value); } } } /** Extract jar file into folder represented by file object. If the JAR contains * files with name filesystem.attributes, it is assumed that these files * has been created by DefaultAttributes implementation and the content * of these files is treated as attributes and added to extracted files. * <p><code>META-INF/</code> directories are skipped over. * * @param fo file object of destination folder * @param is input stream of jar file * @exception IOException if the extraction fails */ public static void extractJar (FileObject fo, InputStream is) throws IOException { JarInputStream jis; JarEntry je; // files with extended attributes (name, DefaultAttributes.Table) HashMap attributes = new HashMap (7); jis = new JarInputStream(is); while ((je = jis.getNextJarEntry()) != null) { String name = je.getName(); if (name.toLowerCase ().startsWith ("meta-inf/")) continue; // NOI18N if (je.isDirectory ()) { createFolder (fo, name); continue; } if (DefaultAttributes.acceptName (name)) { // file with extended attributes DefaultAttributes.Table table = DefaultAttributes.loadTable (jis); attributes.put (name, table); } else { // copy the file FileObject fd = createData(fo, name); FileLock lock = fd.lock (); try { OutputStream os = fd.getOutputStream (lock); try { copy (jis, os); } finally { os.close (); } } finally { lock.releaseLock (); } } } // // apply all extended attributes // Iterator it = attributes.entrySet ().iterator (); while (it.hasNext ()) { Map.Entry entry = (Map.Entry)it.next (); String fileName = (String)entry.getKey (); int last = fileName.lastIndexOf ('/'); String dirName; if (last != -1) dirName = fileName.substring (0, last + 1); else dirName = ""; // NOI18N String prefix = fo.isRoot () ? dirName : fo.getPackageName ('/') + '/' + dirName; DefaultAttributes.Table t = (DefaultAttributes.Table)entry.getValue (); Iterator files = t.keySet ().iterator (); while (files.hasNext ()) { String orig = (String)files.next (); String fn = prefix + orig; FileObject obj = fo.getFileSystem ().findResource (fn); if (obj == null) { continue; } Enumeration attrEnum = t.attrs (orig); while (attrEnum.hasMoreElements ()) { // iterate thru all arguments String attrName = (String)attrEnum.nextElement (); // Note: even transient attributes set here! Object value = t.getAttr (orig, attrName); if (value != null) { obj.setAttribute (attrName, value); } } } } } // extractJar /** Gets the extension of a specified file name. The extension is * everything after the last dot. * * @param fileName name of the file * @return extension of the file (or <code>""</code> if it had none) */ public static String getExtension(String fileName) { int index = fileName.lastIndexOf("."); // NOI18N if (index == -1) return ""; // NOI18N else return fileName.substring(index + 1); } /** Finds an unused file name similar to that requested in the same folder. * The specified file name is used if that does not yet exist. * Otherwise, the first available name of the form <code>basename_nnn.ext</code> (counting from one) is used. * * <p><em>Caution:</em> this method does not lock the parent folder * to prevent race conditions: i.e. it is possible (though unlikely) * that the resulting name will have been created by another thread * just as you were about to create the file yourself (if you are, * in fact, intending to create it just after this call). Since you * cannot currently lock a folder against child creation actions, * the safe approach is to use a loop in which a free name is * retrieved; an attempt is made to {@link FileObject#createData create} * that file; and upon an <code>IOException</code> during * creation, retry the loop up to a few times before giving up. * * @param df parent folder * @param name preferred base name of file * @param ext extension to use * @return a free file name */ public static String findFreeFileName ( FileObject folder, String name, String ext ) { if (folder.getFileObject (name, ext) == null) { return name; } for (int i = 1;;i++) { String destName = name + "_"+i; // NOI18N if (folder.getFileObject (destName, ext) == null) { return destName; } } } /** Finds an unused folder name similar to that requested in the same parent folder. * <p>See caveat for <code>findFreeFileName</code>. * @see #findFreeFileName findFreeFileName * @param df parent folder * @param name preferred folder name * @return a free folder name */ public static String findFreeFolderName ( FileObject folder, String name ) { if (folder.getFileObject (name) == null) { return name; } for (int i = 1;;i++) { String destName = name + "_"+i; // NOI18N if (folder.getFileObject (destName) == null) { return destName; } } } // note: "sister" is preferred in English, please don't ask me why --jglick // NOI18N /** Finds brother file with same base name but different extension. * @param fo the file to find the brother for or <CODE>null</CODE> * @param ext extension for the brother file * @return the brother file (the one with requested extension) or * <CODE>null</CODE> if the brother file does not exists or the original file was <CODE>null</CODE> */ public static FileObject findBrother (FileObject fo, String ext) { if (fo == null) return null; FileObject parent = fo.getParent (); if (parent == null) return null; return parent.getFileObject (fo.getName (), ext); } /** Obtain MIME type for a well-known extension. * @param ext the extension: <code>"jar"</code>, <code>"zip"</code>, etc. Case is unimportant. * @return the MIME type for the extension, or <code>null</code> if the extension is unrecognized */ public static String getMIMEType (String ext) { return (String)map.get (ext.toLowerCase()); } /* mapping of file extensions to content-types */ private static java.util.Dictionary map = new java.util.Hashtable(); /** * Register MIME type for a new extension. * @param ext the file extension (case is unimportant) * @param mimeType the new MIME type * @throws IllegalArgumentException if this extension was already registered with a <em>different</em> MIME type * @see #getMIMEType */ public static void setMIMEType(String ext, String mimeType) { String kk=ext.toLowerCase(); synchronized (map) { String old=(String)map.get(kk); if (old == null) { map.put(kk, mimeType); } else { if (!old.equals(mimeType)) throw new IllegalArgumentException ("Cannot overwrite existing MIME type mapping for extension `" + // NOI18N kk + "' with " + mimeType + " (was " + old + ")"); // NOI18N // else do nothing } } } static { setMIMEType("", "content/unknown"); // NOI18N setMIMEType("uu", "application/octet-stream"); // NOI18N setMIMEType("exe", "application/octet-stream"); // NOI18N setMIMEType("ps", "application/postscript"); // NOI18N setMIMEType("zip", "application/zip"); // NOI18N setMIMEType("class", "application/octet-stream"); // Sun uses application/java-vm // NOI18N setMIMEType("jar", "application/x-jar"); // NOI18N setMIMEType("sh", "application/x-shar"); // NOI18N setMIMEType("tar", "application/x-tar"); // NOI18N setMIMEType("snd", "audio/basic"); // NOI18N setMIMEType("au", "audio/basic"); // NOI18N setMIMEType("wav", "audio/x-wav"); // NOI18N setMIMEType("gif", "image/gif"); // NOI18N setMIMEType("jpg", "image/jpeg"); // NOI18N setMIMEType("jpeg", "image/jpeg"); // NOI18N setMIMEType("htm", "text/html"); // NOI18N setMIMEType("html", "text/html"); // NOI18N setMIMEType("xml", "text/xml"); // NOI18N setMIMEType("xsl", "text/xml"); // NOI18N setMIMEType("dtd", "text/x-dtd"); // NOI18N setMIMEType("text", "text/plain"); // NOI18N setMIMEType("c", "text/plain"); // NOI18N setMIMEType("cc", "text/plain"); // NOI18N setMIMEType("c++", "text/plain"); // NOI18N setMIMEType("h", "text/plain"); // NOI18N setMIMEType("pl", "text/plain"); // NOI18N setMIMEType("txt", "text/plain"); // NOI18N setMIMEType("properties", "text/plain"); // NOI18N setMIMEType("java", "text/x-java"); // NOI18N // mime types from Jetty web server setMIMEType("ra", "audio/x-pn-realaudio"); // NOI18N setMIMEType("ram", "audio/x-pn-realaudio"); // NOI18N setMIMEType("rm", "audio/x-pn-realaudio"); // NOI18N setMIMEType("rpm", "audio/x-pn-realaudio"); // NOI18N setMIMEType("mov", "video/quicktime"); // NOI18N setMIMEType("jsp", "text/plain"); // NOI18N } } /* * Log * 33 Gandalf 1.32 1/20/00 Jesse Glick Bugfix: after the first * file entry in a filesystem.attributes which does not exist, the * remainder of the attributes set is skipped even for files which do * exist. * 32 Gandalf 1.31 1/14/00 Jesse Glick Transient file * attributes. * 31 Gandalf 1.30 1/13/00 Ian Formanek NOI18N * 30 Gandalf 1.29 1/12/00 Ian Formanek NOI18N * 29 Gandalf 1.28 1/6/00 Jesse Glick #5148: extractJar should * not create meta-inf/ dirs. * 28 Gandalf 1.27 12/22/99 Jesse Glick Bugfix: now possible to * set attributes on folders/files at top level of the JAR (null-package * filesystem.attributes). * 27 Gandalf 1.26 12/9/99 Jaroslav Tulach #4347 * 26 Gandalf 1.25 11/25/99 Jaroslav Tulach FileUtil.copy is public. * 25 Gandalf 1.24 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun * Microsystems Copyright in File Comment * 24 Gandalf 1.23 10/1/99 Jaroslav Tulach FileObject.move & * FileObject.copy * 23 Gandalf 1.22 7/28/99 Libor Kramolis * 22 Gandalf 1.21 7/21/99 Libor Kramolis * 21 Gandalf 1.20 6/8/99 Ian Formanek ---- Package Change To * org.openide ---- * 20 Gandalf 1.19 6/8/99 Jaroslav Tulach extractJar * 19 Gandalf 1.18 6/1/99 Petr Jiricka Added MIME type for * properties files * 18 Gandalf 1.17 5/26/99 Martin Ryzl createData added * 17 Gandalf 1.16 5/25/99 Libor Kramolis * 16 Gandalf 1.15 5/20/99 Jaroslav Tulach First version of * MultiFileSystem. * 15 Gandalf 1.14 5/17/99 Miloslav Metelka copyFile with extension * 14 Gandalf 1.13 5/11/99 Jesse Glick Removed obsoleted * comment. * 13 Gandalf 1.12 5/6/99 Petr Jiricka Added MIME types used by * Jetty web server * 12 Gandalf 1.11 5/5/99 Petr Jiricka Added MIME type for * class files - application/octet-stream * 11 Gandalf 1.10 3/31/99 Jaroslav Tulach * 10 Gandalf 1.9 3/15/99 Jesse Glick Utility classes ought * not have public constructors. * 9 Gandalf 1.8 3/11/99 Jaroslav Tulach Works with plain * document. * 8 Gandalf 1.7 2/18/99 Jesse Glick Looking over list of * MIME types... * 7 Gandalf 1.6 2/12/99 Jesse Glick Made setMIMEType() * idempotent: can call several times w/ same args as long as there is no * conflict. * 6 Gandalf 1.5 2/12/99 Jesse Glick Made setSuffix() into * public setMIMEType(). * 5 Gandalf 1.4 2/8/99 Jesse Glick [JavaDoc] * 4 Gandalf 1.3 2/5/99 Jesse Glick [JavaDoc] * 3 Gandalf 1.2 2/4/99 Petr Hamernik setting of extended file * attributes doesn't require FileLock * 2 Gandalf 1.1 1/6/99 Jaroslav Tulach Change of package of * DataObject * 1 Gandalf 1.0 1/5/99 Ian Formanek * $ * Beta Change History: * 0 Tuborg 0.11 --/--/98 Jaroslav Tulach findFreeFolderName added * 0 Tuborg 0.12 --/--/98 Jaroslav Tulach getMimeType */